﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DefaultMetaModelAttributeProvider.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the DefaultMetaModelAttributeProvider type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.ComponentModel.Modelling
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using Collections;
    using Reflection;

    public class DefaultMetaModelAttributeProvider : IMetaModelAttributeProvider
    {
        public static readonly AttributeUsageAttribute DefaultUsageAttribute = new AttributeUsageAttribute(AttributeTargets.All);

        public void InitializeAttributes(MemberInfo member, IList<Attribute> attributes)
        {
            AddMemberAttributes(member, attributes);

#if !SILVERLIGHT

            var metadataAttribute = member.GetCustomAttribute<System.ComponentModel.DataAnnotations.MetadataTypeAttribute>(true);
            if (metadataAttribute != null)
            {
                switch (member.MemberType)
                {
                    case MemberTypes.Property:
                        var metadataMember = metadataAttribute.MetadataClassType.GetMember(member.Name,
                                                                                           MemberTypes.Property | MemberTypes.Field, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance).FirstOrDefault();
                        if (metadataMember != null)
                        {
                            MergeMemberAttributes(attributes, metadataMember.GetCustomAttributes(true).OfType<Attribute>().ToList());
                        }
                        break;
                    case MemberTypes.NestedType:
                    case MemberTypes.TypeInfo:
                        MergeMemberAttributes(attributes, metadataAttribute.MetadataClassType.GetCustomAttributes(true).OfType<Attribute>().ToList());
                        break;
                }
            }

#endif
        }

        protected static void AddMemberAttributes(MemberInfo member, ICollection<Attribute> attributes)
        {
            foreach (var attribute in member.GetCustomAttributes(true).OfType<Attribute>())
            {
                attributes.Add(attribute);
            }
        }

        protected static void MergeMemberAttributes(IList<Attribute> attributes, IList<Attribute> newAttributes)
        {
            foreach (var newAttribute in newAttributes)
            {
                // get usage attribute, falling back to default if no explicit usage is defined
                var usage = newAttribute.GetType().GetCustomAttribute<AttributeUsageAttribute>() ?? DefaultUsageAttribute;
                
                // if AllowMultiple is true or the attribute does not exist in attributes then add; otherwise, replace existing.
                int attributeIndex;
                var currentAttribute = newAttribute;
                if (usage.AllowMultiple || (attributeIndex = attributes.FindIndex(x => x.GetType().Equals(currentAttribute.GetType()))) == -1)
                {
                    attributes.Add(newAttribute);
                }
                else
                {
                    attributes[attributeIndex] = newAttribute;
                }
            }
        }
    }
}